<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="zh-CN"><head><meta http-equiv="Content-Type" content="text/html; charset=utf-8" /><link rel="stylesheet" type="text/css" href="http://storage.360buyimg.com/ebooks/9fd8bb77eb40456b746aaae41785499a_new_.css" /><title>第16章 把AngularJS应用升级到Angular</title></head><body><p class="center"><img src="https://img30.360buyimg.com/ebookadmin/jfs/t20329/6/211810225/66896/2591a74/5b03d540N1888ed20.jpg" class="fullscreen" alt="" href="./image/Figure-016.jpg" /></p><p class="content">如果你使用过一段时间的Angular，那么可能已经有了基于AngularJS的产品。Angular虽然很好，但我们总不能抛弃现有的一切，用Angular重写整个产品吧？更好的做法是对既有的AngularJS应用进行<b>增量式</b>升级。谢天谢地，Angular提供了一种非常棒的方式来实现它！</p><p class="content">AngularJS和Angular的互操作性已经相当完善。在本章中，我们将讨论如何通过写<b>混合式</b>应用的方式来把AngularJS升级到Angular。这种混合式应用中同时运行着AngularJS和Angular框架（它们之间还可以交换数据）。</p><p class="sectionTitle biaoti3"><a id="sec001"></a>16.1 周边概念</p><p class="content">当我们讨论AngularJS和Angular之间的互操作性时，会涉及很多周边概念，下面就是其中的一些。</p><p class="content"><b>把AngularJS的概念映射到Angular</b>：大体上，Angular的组件就是AngularJS的指令。它们也都用到了“服务”。不过本章是讲如何同时使用AngularJS和Angular的，所以我们假设你已经充分了解了这些基础知识。如果你还没怎么用过Angular，请先阅读第3章。</p><p class="content"><b>把AngularJS应用迁移到Angular的准备工作</b>：AngularJS.5提供了新的.component方法来制作“组件型指令”。使用.component有利于为迁移到Angular作准备，另外，创建瘦控制器（或禁止使用ng-controller指令<sup><a class="mz-footnote-link" href="#footnote1"><img alt="" src="https://img30.360buyimg.com/ebookadmin/jfs/t21616/337/214486965/1481/90828627/5b03d540N24da8434.png" href="./image/icon_note.png" /></a></sup>）能把AngularJS应用重构得更好，也更容易与Angular集成。</p><p class="content">准备AngularJS应用的另一个要点是减少或消除双向绑定，更多地使用单向数据流。也就是说，尽量不要通过修改$scope在指令之间传递数据，而是改用服务。</p><p class="content">这些理念确实很重要，有必要进行深入探索，但本章并不会针对升级前的重构阶段讲很多类似的最佳实践。</p><p class="content">本章要讲的是下面这一点。</p><p class="content"><b>写混合式AngularJS/Angular应用</b>：Angular提供了一种方式来启动你的AngularJS应用，然后在其中写Angular的组件和服务。写完Angular组件，只要把它和AngularJS组件混在一起就可以了。另外，依赖注入体系也支持在AngularJS和Angular之间双向传递数据，因此你写的服务在AngularJS和Angular中都能运行。</p><p class="content">它最大的好处是什么呢？因为变更检测是在Zones中运行的，所以你再也不用调用$scope.apply或担心变更检测方面的问题了。</p><p class="sectionTitle biaoti3"><a id="sec002"></a>16.2 我们要构建什么</p><p class="content">在本章中，我们准备升级一个名叫Interest的应用，它模仿了Pinterest（如图16-1所示）。其思想在于你可以保存一枚图钉（pin），即一个带图片的链接。这些图钉会显示在列表中，而且你可以收藏（或取消收藏）一枚图钉。</p><p class="center"><img src="https://img30.360buyimg.com/ebookadmin/jfs/t19126/168/2659742064/118300/9d6afca6/5b03d540N7aac84ee.jpg" class="enlarge" alt="" href="./image/a-149.jpg" /></p><p class="descrip"><b>图16-1　完成后的“山寨版”Pinterest</b></p><p class="reference"><img src="https://img30.360buyimg.com/ebookadmin/jfs/t18559/108/2587195628/17417/8ceb743d/5b03d540N559a889f.jpg" class="inline1" alt="" href="./image/a-9.jpg" />　你可以到code/conversion/AngularJS和code/conversion/hybrid下载AngularJS版和混合版的完整代码。</p><p class="content">在深入讲解之前，我们先来看看AngularJS和Angular互操作的各种使用场景。</p><p class="sectionTitle biaoti3"><a id="sec003"></a>16.3 把AngularJS映射到Angular</p><p class="content">大体来说，AngularJS的五个主要部分是：</p><p class="content">●指令</p><p class="content">●控制器</p><p class="content">●作用域</p><p class="content">●服务</p><p class="content">●依赖注入</p><p class="content">这些在Angular中则发生了显著的变化。你可能听说过，来自Angular核心团队的Igor与Tobias在2014 ngEurope大会上宣布他们将消灭AngularJS中的许多“核心”思想<sup><a class="mz-footnote-link" href="#footnote2"><img alt="" src="https://img30.360buyimg.com/ebookadmin/jfs/t21616/337/214486965/1481/90828627/5b03d540N24da8434.png" href="./image/icon_note.png" /></a></sup>（如图16-2所示）。具体来说，他们宣布Angular将消灭：</p><p class="content">●$scope（以及默认的双向绑定）</p><p class="content">●指令定义对象</p><p class="content">●控制器</p><p class="content">●angular.module</p><p class="center"><img src="https://img30.360buyimg.com/ebookadmin/jfs/t17977/95/2663376234/156468/ab5a6cef/5b03d541N181ac9e3.jpg" class="enlarge" alt="" href="./image/a-150.jpg" /></p><p class="descrip"><b>图16-2　在2014 ngEurope大会上，Igor和Tobias移除了AngularJS.x的很多API。摄影：Michael Bromley（已获授权）</b></p><p class="content">那些使用AngularJS构建应用并习惯于AngularJS思维的人可能会问：如果移除了那些，还剩下什么？没有控制器和$scope怎么能构建Angular应用呢？</p><p class="content">尽管有很多人喜欢夸大Angular的<b>不同之处</b>，但其实它仍然沿袭了AngularJS的大量核心思想。事实上，Angular用<b>一种更简单的模型</b>实现了同样的功能。</p><p class="content">大体上，Angular的核心构造为：</p><p class="content">●组件（可看作指令）</p><p class="content">●服务</p><p class="content">当然，还需要大量的基础设施来支撑它们的工作。比如，你需要用依赖注入体系来管理服务；需要一个强力的变更检测机制，以便在应用中更有效地传播数据变化；还需要一个高效的渲染层，以便在正确的时机渲染DOM。</p><p class="sectionTitle biaoti3"><a id="sec004"></a>16.4 关于互操作性的需求</p><p class="content">那么，有了这两种不同的体系，我们需要借助哪些特性来简化互操作性呢？</p><p class="content">●<b>在AngularJS中使用Angular的组件</b>：我们首先想到的是，要能写出新的Angular组件，并在AngularJS的应用中使用它们。</p><p class="content">●<b>在Angular中使用AngularJS的组件</b>：我们一般不会把整个组件树完全替换成Angular的组件，而是在Angular组件<b>之中</b>复用那些AngularJS组件。</p><p class="content">●<b>服务共享</b>：假设我们有一个UserService，想要在AngularJS和Angular之间共享它。服务通常就是一个普通的JavaScript对象，因此更抽象地说，我们需要的是一个能支持互操作的<b>依赖注入</b>系统。</p><p class="content">●<b>变更检测</b>：如果我们在某一边进行了改动，这些变更也应该能传播到另一边。</p><p class="content">Angular提供了所有这些场景的解决方案，本章将一一讲解。</p><p class="content">在本章中，我们会：</p><p class="content">●描述即将升级的AngularJS应用；</p><p class="content">●解释如何用Angular的UpgradeAdapter来组织混合式应用；</p><p class="content">●通过把AngularJS应用转化成混合式应用来一步步解释如何在AngularJS和Angular中共享组件（指令）与服务。</p><p class="sectionTitle biaoti3"><a id="sec005"></a>16.5 AngularJS应用</p><p class="content">作为准备，我们先重温一下该应用的AngularJS版本。</p><p class="center"><img src="https://img30.360buyimg.com/ebookadmin/jfs/t17728/48/2715397358/11728/16045511/5b03d541N9720f866.jpg" class="enlarge" alt="" href="./image/a-4.jpg" /></p><p class="content">本章假设你已经具备了关于AngularJS和ui-router<sup><a class="mz-footnote-link" href="#footnote3"><img alt="" src="https://img30.360buyimg.com/ebookadmin/jfs/t21616/337/214486965/1481/90828627/5b03d540N24da8434.png" href="./image/icon_note.png" /></a></sup>的知识。如果你对AngularJS感到吃力，请先阅读《AngularJS权威教程》<sup><a class="mz-footnote-link" href="#footnote4"><img alt="" src="https://img30.360buyimg.com/ebookadmin/jfs/t21616/337/214486965/1481/90828627/5b03d540N24da8434.png" href="./image/icon_note.png" /></a></sup>。</p><p class="content">我们不会深入剖析和解释每个AngularJS的概念，只会回顾一下这个准备升级到Angular/混合式应用的结构。</p><p class="content">要运行AngularJS应用，使用cd转到示例代码中的conversion/AngularJS，安装依赖，并运行该应用：</p><p class="daima">cd code/conversion/AngularJS　# change directories</p><p class="daima">npm install　　　　　　　　　 # install dependencies</p><p class="daima">npm run go　　　　　　　　　　# run the app</p><p class="content">如果没有自动打开浏览器，请手动打开URL：http：//localhost：8080。</p><p class="content">在该应用中，你可以看到用户正在收集的小玩偶。我们可以把鼠标移到某个条目上，并点击红心图标来收藏一个图钉，如图16-3所示。</p><p class="center"><img src="https://img30.360buyimg.com/ebookadmin/jfs/t20341/11/218547857/105644/a2b27283/5b03d541N792c492e.jpg" class="enlarge" alt="" href="./image/a-151.jpg" /></p><p class="descrip"><b>图16-3　红心表示已收藏的图钉</b></p><p class="content">我们还可以导航到/add页，并添加一个新的图钉。试试提交这个默认表单。</p><p class="reference"><img src="https://img30.360buyimg.com/ebookadmin/jfs/t18559/108/2587195628/17417/8ceb743d/5b03d540N559a889f.jpg" class="inline1" alt="" href="./image/a-9.jpg" />　处理图片上传对于这个演示来说过于复杂了。目前，如果你想换一幅图，只要粘贴一幅图片的完整URL即可。</p><p class="listTitle1 biaoti8">16.5.1 AngularJS应用的HTML</p><p class="content">AngularJS应用中的index.html使用了一种常用的结构。</p><p class="reference"><b>code/conversion/AngularJS/index.html</b></p><p class="daima">&lt;！DOCTYPE html&gt;</p><p class="daima">&lt;html ng-app='interestApp'&gt;</p><p class="daima">&lt;head&gt;</p><p class="daima">　&lt;meta charset="utf-8"&gt;</p><p class="daima">　&lt;title&gt;Interest&lt;/title&gt;</p><p class="daima">　&lt;link rel="stylesheet" href="css/bootstrap.min.css"&gt;</p><p class="daima">　&lt;link rel="stylesheet" href="css/sf.css"&gt;</p><p class="daima">　&lt;link rel="stylesheet" href="css/interest.css"&gt;</p><p class="daima">&lt;/head&gt;</p><p class="daima">&lt;body class="container-fullwidth"&gt;</p><p class="daima">　&lt;div class="page-header"&gt;</p><p class="daima">　　&lt;div class="container"&gt;</p><p class="daima">　　　&lt;h1&gt;Interest &lt;small&gt;what you're interested in&lt;/small&gt;&lt;/h1&gt;</p><p class="daima">　　　&lt;div class="navLinks"&gt;</p><p class="daima">　　　　&lt;a ui-sref='home' id="navLinkHome"&gt;Home&lt;/a&gt;</p><p class="daima">　　　　&lt;a ui-sref='add' id="navLinkAdd"&gt;Add&lt;/a&gt;</p><p class="daima">　　　&lt;/div&gt;</p><p class="daima">　　&lt;/div&gt;</p><p class="daima">　&lt;/div&gt;</p><p class="daima">　&lt;div id="content"&gt;</p><p class="daima">　　&lt;div ui-view=''&gt;&lt;/div&gt;</p><p class="daima">　&lt;/div&gt;</p><p class="daima">　&lt;script src="js/vendor/lodash.js"&gt;&lt;/script&gt;</p><p class="daima">　&lt;script src="js/vendor/angular.js"&gt;&lt;/script&gt;</p><p class="daima">　&lt;script src="js/vendor/angular-ui-router.js"&gt;&lt;/script&gt;</p><p class="daima">　&lt;script src="js/app.js"&gt;&lt;/script&gt;</p><p class="daima">&lt;/body&gt;</p><p class="daima">&lt;/html&gt;</p><p class="content">●注意，我们在html标签中使用ng-app来指定该应用所用的是interestApp模块。</p><p class="content">●我们在body的底部使用script标签来加载JavaScript脚本。</p><p class="content">●该模板包含一个page-header指令，这里是我们的导航栏。</p><p class="content">●我们使用了ui-router，这意味着：</p><p class="content">●使用ui-sref来表示链接（Home和Add）；</p><p class="content">●我们希望路由器把内容放在ui-view中。</p><p class="listTitle1 biaoti8">16.5.2 代码概览</p><p class="content">我们将遍历代码中的每个部分。不过首先来简单描述一下这些活动部件。</p><p class="content">在我们的应用中，有两个路由：</p><p class="content">●/使用HomeController；</p><p class="content">●/add使用AddController。</p><p class="content">我们用一个PinsService来存放所有现有图钉的数组。HomeController渲染出图钉列表，而AddController把新的元素添加到列表中。</p><p class="content">我们的根路由使用HomeController来渲染这些图钉，而我们用pin指令来渲染单个图钉。</p><p class="content">PinsService用于存放应用中的数据，所以先来看看它。</p><p class="listTitle1 biaoti8">16.5.3 AngularJS：PinsService</p><p class="reference"><b>code/conversion/AngularJS/js/app.js</b></p><p class="daima">angular.module（'interestApp', ['ui.router']）</p><p class="daima">.service（'PinsService', function（$http, $q）{</p><p class="daima">　this._pins = null;</p><p class="daima">　this.pins = function（）{</p><p class="daima">　　var self = this;</p><p class="daima">　　if（self._pins == null）{</p><p class="daima">　　　// initialize with sample data</p><p class="daima">　　　return $http.get（"/js/data/sample-data.json"）.then（</p><p class="daima">　　　　function（response）{</p><p class="daima">　　　　　self._pins = response.data;</p><p class="daima">　　　　　return self._pins;</p><p class="daima">　　　　}）</p><p class="daima">　　} else {</p><p class="daima">　　　return $q.when（self._pins）;</p><p class="daima">　　}</p><p class="daima">　}</p><p class="daima">　this.addPin = function（newPin）{</p><p class="daima">　　// adding would normally be an API request so lets mock async</p><p class="daima">　　return $q.when（</p><p class="daima">　　　this._pins.unshift（newPin）</p><p class="daima">　　）;</p><p class="daima">　}</p><p class="daima">}）</p><p class="content">PinsService是一个.service，它把这些图钉的数组保存在属性_.pins中。</p><p class="content">.pins方法返回一个承诺，它会被解析（resolve）成一个图钉列表。如果_.pins为null（也就是首次访问时），我们就会从/js/data/sample-data.json中加载示例数据。</p><p class="reference"><b>code/conversion/AngularJS/js/data/sample-data.json</b></p><p class="daima">[</p><p class="daima">　{</p><p class="daima">　　"title"："sock puppets",</p><p class="daima">　　"description"："from：\nThe FunCraft Book of Puppets\n1976\nISBN：0-590-11936\</p><p class="daima">-2",</p><p class="daima">　　"user_name"："tofutti break",</p><p class="daima">　　"avatar_src"："images/avatars/42826303@N00.jpg",</p><p class="daima">　　"src"："images/pins/106033588_167d811702_o.jpg",</p><p class="daima">　　"url"："https：//www.flickr.com/photos/tofuttibreak/106033588/",</p><p class="daima">　　"faved"：false,</p><p class="daima">　　"id"："106033588"</p><p class="daima">　},</p><p class="daima">　{</p><p class="daima">　　"title"："Puppet play.",</p><p class="daima">　　"description"："My wife's handmade.",</p><p class="daima">　　"user_name"："MIKI Yoshihito（´ω）",</p><p class="daima">　　"avatar_src"："images/avatars/7940758@N07.jpg",</p><p class="daima">　　"src"："images/pins/4422575066_7d5c4c41e7_o.jpg",</p><p class="daima">　　"url"："https：//www.flickr.com/photos/mujitra/4422575066/",</p><p class="daima">　　"faved"：false,</p><p class="daima">　　"id"："4422575066"</p><p class="daima">　},</p><p class="daima">　{</p><p class="daima">　　"title"："easy to make puppets - oliver owl（detail）",</p><p class="daima">　　"description"："from easy to make puppets by joyce luckin（1975）",</p><p class="daima">　　"user_name"："gilliflower",</p><p class="daima">　　"avatar_src"："images/avatars/26265986@N00.jpg",</p><p class="daima">　　"src"："images/pins/6819859061_25d05ef2e1_o.jpg",</p><p class="daima">　　"url"："https：//www.flickr.com/photos/gilliflower/6819859061/",</p><p class="daima">　　"faved"：false,</p><p class="daima">　　"id"："6819859061"</p><p class="daima">　},</p><p class="content">.addPin方法把一个新图钉加入到图钉数组中。在这里，我们使用$q.when来返回一个承诺，就像我们真的向一台服务器发起异步调用时一样。</p><p class="listTitle1 biaoti8">16.5.4 AngularJS：配置路由</p><p class="content">我们准备用ui-router来配置这些路由。</p><p class="reference"><img src="https://img30.360buyimg.com/ebookadmin/jfs/t18559/108/2587195628/17417/8ceb743d/5b03d540N559a889f.jpg" class="inline1" alt="" href="./image/a-9.jpg" />　如果你还不熟悉ui-router，请到https：//github.com/angular-ui/ui-router/wiki阅读文档。</p><p class="content">正如前面所说，我们有两个路由。</p><p class="reference"><b>code/conversion/AngularJS/js/app.js</b></p><p class="daima">.config（function（$stateProvider, $urlRouterProvider）{</p><p class="daima">　$stateProvider</p><p class="daima">　　.state（'home', {</p><p class="daima">　　　templateUrl：'/templates/home.html',</p><p class="daima">　　　controller：'HomeController as ctrl',</p><p class="daima">　　　url：'/',</p><p class="daima">　　　resolve：{</p><p class="daima">　　　　'pins'：function（PinsService）{</p><p class="daima">　　　　　return PinsService.pins（）;</p><p class="daima">　　　　}</p><p class="daima">　　　}</p><p class="daima">　　}）</p><p class="daima">　　.state（'add', {</p><p class="daima">　　　templateUrl：'/templates/add.html',</p><p class="daima">　　　controller：'AddController as ctrl',</p><p class="daima">　　　url：'/add',</p><p class="daima">　　　resolve：{</p><p class="daima">　　　　'pins'：function（PinsService）{</p><p class="daima">　　　　　return PinsService.pins（）;</p><p class="daima">　　　　}</p><p class="daima">　　　}</p><p class="daima">　　}）</p><p class="daima">　　$urlRouterProvider.when（'', '/'）;</p><p class="daima">}）</p><p class="content">第一个路由/被映射到了HomeController，我们很快就会看到它的模板。注意，我们还在使用ui-router的resolve功能。这表示在为用户加载此路由之前，我们希望先调用PinsService.pins（），并且把结果（图钉列表）注入到控制器中（HomeController）。</p><p class="content">/add路由与之类似，只是使用了另一套模板和控制器。</p><p class="content">我们首先看看HomeController。</p><p class="listTitle1 biaoti8">16.5.5 AngularJS：HomeController</p><p class="content">HomeController很简明。我们把通过resolve注入进来的pins保存到$scope.pins中。</p><p class="reference"><b>code/conversion/AngularJS/js/app.js</b></p><p class="daima">.controller（'HomeController', function（pins）{</p><p class="daima">　this.pins = pins;</p><p class="daima">}）</p><p class="listTitle1 biaoti8">16.5.6 AngularJS：HomeController模板</p><p class="content">首页的模板很小：我们用ng-repeat来循环$scope.pins中的图钉，然后用pin指令来渲染出每个图钉。</p><p class="reference"><b>code/conversion/AngularJS/templates/home.html</b></p><p class="daima">&lt;div class="container"&gt;</p><p class="daima">　&lt;div class="row"&gt;</p><p class="daima">　　&lt;pin item="pin" ng-repeat="pin in ctrl.pins"&gt;</p><p class="daima">　　&lt;/pin&gt;</p><p class="daima">　&lt;/div&gt;</p><p class="daima">&lt;/div&gt;</p><p class="content">下面深入看看这个pin指令。</p><p class="listTitle1 biaoti8">16.5.7 AngularJS：pin指令</p><p class="content">pin指令被限制（restrict）为匹配元素（E），并且具有一个template。</p><p class="content">我们可以通过item属性把pin传进去，就像在home.html模板中所做的那样。</p><p class="content">link函数在定义域上定义了一个名叫toggleFav的函数，它会来回切换图钉的faved属性。</p><p class="reference"><b>code/conversion/AngularJS/js/app.js</b></p><p class="daima">}）</p><p class="daima">.directive（'pin', function（）{</p><p class="daima">　return {</p><p class="daima">　　restrict：'E',</p><p class="daima">　　templateUrl：'/templates/pin.html',</p><p class="daima">　　scope：{</p><p class="daima">　　　'pin'："=item"</p><p class="daima">　　},</p><p class="daima">　　link：function（scope, elem, attrs）{</p><p class="daima">　　　scope.toggleFav = function（）{</p><p class="daima">　　　　scope.pin.faved =！scope.pin.faved;</p><p class="daima">　　　}</p><p class="daima">　　}</p><p class="daima">　}</p><p class="daima">}）</p><p class="center"><img src="https://img30.360buyimg.com/ebookadmin/jfs/t18559/108/2587195628/17417/8ceb743d/5b03d540N559a889f.jpg" class="enlarge" alt="" href="./image/a-9.jpg" /></p><p class="content">到2016年，该指令已经不能再作为指令最佳实践的示例了。比如，要想在AngularJS中写一个全新的指令，我应该会用AngularJS.5中新的.component函数。至少，我会用controllerAs来代替link。</p><p class="content">但本节并不是讲解该如何写好AngularJS代码的，而是展示如何迁移现有的AngularJS代码。</p><p class="listTitle1 biaoti8">16.5.8 AngularJS：pin指令模板</p><p class="content">templates/pin.html模板在我们的页面中渲染了一个单独的图钉。</p><p class="reference"><b>code/conversion/AngularJS/templates/pin.html</b></p><p class="daima">&lt;div class="col-sm-6 col-md-4"&gt;</p><p class="daima">　&lt;div class="thumbnail"&gt;</p><p class="daima">　　&lt;div class="content"&gt;</p><p class="daima">　　　&lt;img ng-src="{{pin.src}}" class="img-responsive"&gt;</p><p class="daima">　　　&lt;div class="caption"&gt;</p><p class="daima">　　　　&lt;h3&gt;{{pin.title}}&lt;/h3&gt;</p><p class="daima">　　　　&lt;p&gt;{{pin.description | truncate：100}}&lt;/p&gt;</p><p class="daima">　　　&lt;/div&gt;</p><p class="daima">　　　&lt;div class="attribution"&gt;</p><p class="daima">　　　　&lt;img ng-src="{{pin.avatar_src}}" class="img-circle"&gt;</p><p class="daima">　　　　&lt;h4&gt;{{pin.user_name}}&lt;/h4&gt;</p><p class="daima">　　　&lt;/div&gt;</p><p class="daima">　　&lt;/div&gt;</p><p class="daima">　　&lt;div class="overlay"&gt;</p><p class="daima">　　　&lt;div class="controls"&gt;</p><p class="daima">　　　　&lt;div class="heart"&gt;</p><p class="daima">　　　　　&lt;a ng-click="toggleFav（）"&gt;</p><p class="daima">　　　　　　&lt;img src="/images/icons/Heart-Empty.png" ng-if="！pin.faved"&gt;&lt;/img&gt;</p><p class="daima">　　　　　　&lt;img src="/images/icons/Heart-Red.png" ng-if="pin.faved"&gt;&lt;/img&gt;</p><p class="daima">　　　　　&lt;/a&gt;</p><p class="daima">　　　　&lt;/div&gt;</p><p class="daima">　　　&lt;/div&gt;</p><p class="daima">　　&lt;/div&gt;</p><p class="daima">　&lt;/div&gt;</p><p class="daima">&lt;/div&gt;</p><p class="content">我们在这里用到的指令都是AngularJS的内置指令：</p><p class="content">●用ng-src来渲染img；</p><p class="content">●接着显示pin.title和pin.description；</p><p class="content">●用ng-if来决定是显示红心还是空心。</p><p class="content">这里最有意思的是ng-click，它会调用toggleFav，而toggleFav会修改pin.faved属性，应用从而据此显示红心或空心（如图16-4所示）。</p><p class="center"><img src="https://img30.360buyimg.com/ebookadmin/jfs/t21043/24/213618491/21755/8edccdac/5b03d540Nbac9f29c.jpg" class="enlarge" alt="" href="./image/a-152.jpg" /></p><p class="descrip"><b>图16-4　红心与空心</b></p><p class="content">接下来，我们看看AddController。</p><p class="listTitle1 biaoti8">16.5.9 AngularJS：AddController</p><p class="content">这个AddController比HomeController的代码要多一点。我们从定义控制器并指定要注入的服务开始。</p><p class="reference"><b>code/conversion/AngularJS/js/app.js</b></p><p class="daima">.controller（'AddController', function（$state, PinsService, $timeout）{</p><p class="daima">　var ctrl = this;</p><p class="daima">　ctrl.saving = false;</p><p class="content">我们在路由器和模板中使用了controllerAs语法。这意味着我们把属性放在了this上，而不是$scope上。this的作用域在ES5 JavaScript上有点复杂，所以我们指定var ctrl = this;，以消除在嵌套的函数中引用该控制器时可能出现的歧义。</p><p class="reference"><b>code/conversion/AngularJS/js/app.js</b></p><p class="daima">　var makeNewPin = function（）{</p><p class="daima">　　return {</p><p class="daima">　　　"title"："Steampunk Cat",</p><p class="daima">　　　"description"："A cat wearing goggles",</p><p class="daima">　　　"user_name"："me",</p><p class="daima">　　　"avatar_src"："images/avatars/me.jpg",</p><p class="daima">　　　"src"："/images/pins/cat.jpg",</p><p class="daima">　　　"url"："http：//cats.com",</p><p class="daima">　　　"faved"：false,</p><p class="daima">　　　"id"：Math.floor（Math.random（）* 10000）.toString（）</p><p class="daima">　　}</p><p class="daima">　}</p><p class="daima">　ctrl.newPin = makeNewPin（）;</p><p class="content">我们创建了一个makeNewPin函数，它包含了图钉的默认构造函数和数据。</p><p class="content">我们还通过把ctrl.newPin属性设置为该函数的调用结果初始化了该控制器。</p><p class="content">最后，我们要定义一个函数来提交新图钉。</p><p class="reference"><b>code/conversion/AngularJS/js/app.js</b></p><p class="daima">　ctrl.submitPin = function（）{</p><p class="daima">　　ctrl.saving = true;</p><p class="daima">　　$timeout（function（）{</p><p class="daima">　　　PinsService.addPin（ctrl.newPin）.then（function（）{</p><p class="daima">　　　　ctrl.newPin = makeNewPin（）;</p><p class="daima">　　　　ctrl.saving = false;</p><p class="daima">　　　　$state.go（'home'）;</p><p class="daima">　　　}）;</p><p class="daima">　　}, 2000）;</p><p class="daima">　}</p><p class="daima">}）</p><p class="content">本质上，该文档调用PinsService.addPin创建了一个新的图钉。不过这里还做了一些别的事情。</p><p class="content">在真实的应用中，这类操作几乎总会向服务器发起一次调用。这里我们使用$timeout来模拟此效果。（实际上，你也可以移除$timeout函数，程序仍然能正常工作。在这里调用它是为了延缓程序的响应速度，让我们有机会看见Saving……提示。）</p><p class="content">我们要给用户一些提示，好让他们知道我们正在保存图钉，因此设置ctrl.saving = true。</p><p class="content">我们调用PinsService.addPin，并把ctrl.newPin传给它。addPin会返回一个承诺，我们在这个承诺的回调函数中：</p><p class="content">（1）把ctrl.newPin恢复成原始值；</p><p class="content">（2）把ctrl.saving设置为false，因为已经保存好了图钉；</p><p class="content">（3）使用$state服务把用户重定向到首页去，在那里可以看到新的图钉。</p><p class="content">下面是AddController的完整代码。</p><p class="reference"><b>code/conversion/AngularJS/js/app.js</b></p><p class="daima">.controller（'AddController', function（$state, PinsService, $timeout）{</p><p class="daima">　var ctrl = this;</p><p class="daima">　ctrl.saving = false;</p><p class="daima">　var makeNewPin = function（）{</p><p class="daima">　　return {</p><p class="daima">　　　"title"："Steampunk Cat",</p><p class="daima">　　　"description"："A cat wearing goggles",</p><p class="daima">　　　"user_name"："me",</p><p class="daima">　　　"avatar_src"："images/avatars/me.jpg",</p><p class="daima">　　　"src"："/images/pins/cat.jpg",</p><p class="daima">　　　"url"："http：//cats.com",</p><p class="daima">　　　"faved"：false,</p><p class="daima">　　　"id"：Math.floor（Math.random（）* 10000）.toString（）</p><p class="daima">　　}</p><p class="daima">　}</p><p class="daima">　ctrl.newPin = makeNewPin（）;</p><p class="daima">　ctrl.submitPin = function（）{</p><p class="daima">　　ctrl.saving = true;</p><p class="daima">　　$timeout（function（）{</p><p class="daima">　　　PinsService.addPin（ctrl.newPin）.then（function（）{</p><p class="daima">　　　　ctrl.newPin = makeNewPin（）;</p><p class="daima">　　　　ctrl.saving = false;</p><p class="daima">　　　　$state.go（'home'）;</p><p class="daima">　　　}）;</p><p class="daima">　　}, 2000）;</p><p class="daima">　}</p><p class="daima">}）</p><p class="listTitle1 biaoti8">16.5.10 AngularJS：AddController模板</p><p class="content">/add路由会渲染add.html模板。</p><p class="center"><img src="https://img30.360buyimg.com/ebookadmin/jfs/t18535/84/2648756402/63226/49e3936a/5b03d540Ne89bed10.jpg" class="enlarge" alt="" href="./image/a-153.jpg" /></p><p class="descrip"><b>图16-5　新增图钉的表单</b></p><p class="content">该模板使用ng-model来把input标签绑定到控制器上的newPin属性。</p><p class="content">这里值得关注的是：</p><p class="content">●我们在提交按钮上使用ng-click来调用ctrl.submitPin；</p><p class="content">●如果ctrl.saving为真，那么就要显示一条Saving……消息。</p><p class="reference"><b>code/conversion/AngularJS/templates/add.html</b></p><p class="daima">&lt;div class="container"&gt;</p><p class="daima">　&lt;div class="row"&gt;</p><p class="daima">　　&lt;form class="form-horizontal"&gt;</p><p class="daima">　　　&lt;div class="form-group"&gt;</p><p class="daima">　　　　&lt;label for="title"</p><p class="daima">　　　　　　　 class="col-sm-2 control-label"&gt;Title&lt;/label&gt;</p><p class="daima">　　　　&lt;div class="col-sm-10"&gt;</p><p class="daima">　　　　　&lt;input type="text"</p><p class="daima">　　　　　　　　 class="form-control"</p><p class="daima">　　　　　　　　 id="title"</p><p class="daima">　　　　　　　　 placeholder="Title"</p><p class="daima">　　　　　　　　 ng-model="ctrl.newPin.title"&gt;</p><p class="daima">　　　&lt;/div&gt;</p><p class="daima">　　&lt;/div&gt;</p><p class="daima">　　&lt;div class="form-group"&gt;</p><p class="daima">　　　&lt;label for="description"</p><p class="daima">　　　　　　 class="col-sm-2 control-label"&gt;Description&lt;/label&gt;</p><p class="daima">　　　&lt;div class="col-sm-10"&gt;</p><p class="daima">　　　　&lt;input type="text"</p><p class="daima">　　　　　　　 class="form-control"</p><p class="daima">　　　　　　　 id="description"</p><p class="daima">　　　　　　　 placeholder="Description"</p><p class="daima">　　　　　　　 ng-model="ctrl.newPin.description"&gt;</p><p class="daima">　　　&lt;/div&gt;</p><p class="daima">　　&lt;/div&gt;</p><p class="daima">　　&lt;div class="form-group"&gt;</p><p class="daima">　　　&lt;label for="url"</p><p class="daima">　　　　　　 class="col-sm-2 control-label"&gt;Link URL&lt;/label&gt;</p><p class="daima">　　　&lt;div class="col-sm-10"&gt;</p><p class="daima">　　　　&lt;input type="text"</p><p class="daima">　　　　　　　 class="form-control"</p><p class="daima">　　　　　　　 id="url"</p><p class="daima">　　　　　　　 placeholder="Link URL"</p><p class="daima">　　　　　　　 ng-model="ctrl.newPin.url"&gt;</p><p class="daima">　　　&lt;/div&gt;</p><p class="daima">　　&lt;/div&gt;</p><p class="daima">　　　&lt;div class="form-group"&gt;</p><p class="daima">　　　　&lt;label for="url"</p><p class="daima">　　　　　　　 class="col-sm-2 control-label"&gt;Image URL&lt;/label&gt;</p><p class="daima">　　　　&lt;div class="col-sm-10"&gt;</p><p class="daima">　　　　　&lt;input type="text"</p><p class="daima">　　　　　　　　 class="form-control"</p><p class="daima">　　　　　　　　 id="url"</p><p class="daima">　　　　　　　　 placeholder="Image URL"</p><p class="daima">　　　　　　　　 ng-model="ctrl.newPin.src"&gt;</p><p class="daima">　　　　&lt;/div&gt;</p><p class="daima">　　　&lt;/div&gt;</p><p class="daima">　　　&lt;div class="form-group"&gt;</p><p class="daima">　　　　&lt;div class="col-sm-offset-2 col-sm-10"&gt;</p><p class="daima">　　　　　&lt;button type="submit"</p><p class="daima">　　　　　　　　　class="btn btn-default"</p><p class="daima">　　　　　　　　　ng-click="ctrl.submitPin（）"&gt;Submit&lt;/button&gt;</p><p class="daima">　　　　&lt;/div&gt;</p><p class="daima">　　　&lt;/div&gt;</p><p class="daima">　　　&lt;div ng-if="ctrl.saving"&gt;</p><p class="daima">　　　　Saving……</p><p class="daima">　　　&lt;/div&gt;</p><p class="daima">　　&lt;/form&gt;</p><p class="daima">　&lt;/div&gt;</p><p class="daima">&lt;/div&gt;</p><p class="listTitle1 biaoti8">16.5.11 AngularJS：总结</p><p class="content">我们终于有了要升级的AngularJS应用。该应用的复杂度正好能让我们演示如何向Angular迁移。</p><p class="sectionTitle biaoti3"><a id="sec006"></a>16.6 构建混合式应用</p><p class="content">现在，我们已经为往现有的AngularJS应用中引入一些Angular的技术作好了准备。</p><p class="content">开始在浏览器中使用Angular之前，我们需要对应用的结构进行一些调整。</p><p class="reference"><img src="https://img30.360buyimg.com/ebookadmin/jfs/t18559/108/2587195628/17417/8ceb743d/5b03d540N559a889f.jpg" class="inline1" alt="" href="./image/a-9.jpg" />　你可以在code/conversion/hybrid找到这些示例代码。</p><p class="listTitle1 biaoti8">16.6.1 混合式应用的结构</p><p class="content">创建混合式应用的第一步是确保你同时加载了AngularJS和Angular的依赖。不过每个人遇到的具体情况可能会略有不同。</p><p class="content">在这个例子中，我们已经提供了AngularJS的库（在js/vendor中）。接下来还要从npm中加载Angular的库。</p><p class="content">在你的项目中，可能需要同时提供这两个库，比如使用Bower<sup><a class="mz-footnote-link" href="#footnote5"><img alt="" src="https://img30.360buyimg.com/ebookadmin/jfs/t21616/337/214486965/1481/90828627/5b03d540N24da8434.png" href="./image/icon_note.png" /></a></sup>等。不过对于Angular来说，用npm更省事，而且我们也建议使用npm来安装Angular。</p><p class="content">●<b>用package.json指定依赖</b></p><p class="content">你可以通过npm来根据文件package.json安装依赖。下面是这个混合式应用例子中的package.json。</p><p class="reference"><b>code/conversion/hybrid/package.json</b></p><p class="daima">{</p><p class="daima">　"name"："ng-hybrid-pinterest",</p><p class="daima">　"version"："0.0.1",</p><p class="daima">　"description"："toy pinterest clone in AngularJS/Angular hybrid",</p><p class="daima">　"contributors"：[</p><p class="daima">　　"Nate Murray &lt;nate@fullstack.io&gt;",</p><p class="daima">　　"Felipe Coury &lt;felipe@ng-book.com&gt;"</p><p class="daima">　],</p><p class="daima">　"main"："index.js",</p><p class="daima">　"private"：true,</p><p class="daima">　"scripts"：{</p><p class="daima">　　"clean"："rm -f ts/*.js ts/*.js.map ts/components/*.js ts/components/*.js.ma\</p><p class="daima">p ts/services/*.js ts/services.js.map",</p><p class="daima">　　"tsc"："./node_modules/.bin/tsc",</p><p class="daima">　　"tsc：w"："./node_modules/.bin/tsc -w",</p><p class="daima">　　"serve"："./node_modules/.bin/live-server ——host=localhost ——port=8080 .",</p><p class="daima">　　"e2e：serve"："npm run tsc &amp;&amp; ./node_modules/.bin/live-server ——host=localhos\</p><p class="daima">t ——port=8080 ——no-browser .",</p><p class="daima">　　"go"："concurrent \"npm run tsc：w\" \"npm run serve\" "</p><p class="daima">　},</p><p class="daima">　"dependencies"：{</p><p class="daima">　　"@angular/common"："2.4.1",</p><p class="daima">　　"@angular/compiler"："2.4.1",</p><p class="daima">　　"@angular/core"："2.4.1",</p><p class="daima">　　"@angular/forms"："2.4.1",</p><p class="daima">　　"@angular/http"："2.4.1",</p><p class="daima">　　"@angular/platform-browser"："2.4.1",</p><p class="daima">　　"@angular/platform-browser-dynamic"："2.4.1",</p><p class="daima">　　"@angular/router"："3.4.1",</p><p class="daima">　　"@angular/upgrade"："2.0.0-rc.6",</p><p class="daima">　　"@types/jasmine"："2.5.40",</p><p class="daima">　　"core-js"："2.4.1",</p><p class="daima">　　"es6-shim"："0.35.0",</p><p class="daima">　　"reflect-metadata"："0.1.9",</p><p class="daima">　　"rxjs"："5.0.2",</p><p class="daima">　　"systemjs"："0.19.6",</p><p class="daima">　　"ts-helpers"："1.1.1",</p><p class="daima">　　"tslint"："3.7.0-dev.2",</p><p class="daima">　　"typings"："0.8.1",</p><p class="daima">　　"zone.js"："0.7.4"</p><p class="daima">　},</p><p class="daima">　"devDependencies"：{</p><p class="daima">　　"@types/jasmine"："2.2.30",</p><p class="daima">　　"@types/node"："6.0.42",</p><p class="daima">　　"concurrently"："1.0.0",</p><p class="daima">　　"jasmine-spec-reporter"："2.5.0",</p><p class="daima">　　"karma"："0.12.22",</p><p class="daima">　　"karma-chrome-launcher"："0.1.4",</p><p class="daima">　　"karma-jasmine"："0.1.5",</p><p class="daima">　　"live-server"："0.9.0",</p><p class="daima">　　"protractor"："4.0.14",</p><p class="daima">　　"ts-node"："1.2.1",</p><p class="daima">　　"typescript"："2.0.3"</p><p class="daima">　}</p><p class="daima">}</p><p class="reference"><img src="https://img30.360buyimg.com/ebookadmin/jfs/t18559/108/2587195628/17417/8ceb743d/5b03d540N559a889f.jpg" class="inline1" alt="" href="./image/a-9.jpg" />　如果你不熟悉其中的某个包，最好自己去发现它的用途。比如rxjs是一个为我们提供可观察对象的库，而systemjs提供的是模块加载器，我们将在本章中用到它。</p><p class="content">一旦添加了Angular的依赖，就可以运行npm install命令来安装它们了。</p><p class="content">●<b>编译代码</b></p><p class="content">你可能注意到了，package.json中的"script"属性中包含另一个属性"tsc"。这表示当我们运行命令npm run tsc时，它就会调用TypeScript编译器来编译我们的代码。</p><p class="content">我们准备在这个例子中使用TypeScript，同时AngularJS的代码仍然使用JavaScript。</p><p class="content">要这么做，就要先把所有TypeScript代码放进ts/文件夹里，把所有JavaScript代码放进js/文件夹里。</p><p class="content">我们用tsconfig.json文件来配置TypeScript编译器。关于此文件，现在你只要知道一点就可以了：filesGlob属性指定了适配规则"./ts/**/*.ts"。它的意思是“当运行TypeScript编译器时，我们希望编译ts/目录下所有以.ts结尾的文件”。</p><p class="content">在该项目中，<b>浏览器只会加载JavaScript</b>。因此我们要使用TypeScript编译器（tsc）来把这些代码编译成JavaScript，然后再把AngularJS和Angular的JavaScript代码加载进浏览器中。</p><p class="content">●<b>加载index.html依赖</b></p><p class="content">现在，我们已经设置好了依赖和编译器，接着就要把这些JavaScript文件加载到浏览器中了。因此，我们添加script标签。</p><p class="reference"><b>code/conversion/AngularJS/hybrid/index.html</b></p><p class="daima">　&lt;div id="content"&gt;</p><p class="daima">　　&lt;div ui-view=''&gt;&lt;/div&gt;</p><p class="daima">　&lt;/div&gt;</p><p class="daima">　&lt;！—— Libraries ——&gt;</p><p class="daima">　&lt;script src="node_modules/core-js/client/shim.min.js"&gt;&lt;/script&gt;</p><p class="daima">　&lt;script src="node_modules/zone.js/dist/zone.js"&gt;&lt;/script&gt;</p><p class="daima">　&lt;script src="node_modules/reflect-metadata/Reflect.js"&gt;&lt;/script&gt;</p><p class="daima">　&lt;script src="node_modules/systemjs/dist/system.src.js"&gt;&lt;/script&gt;</p><p class="daima">　&lt;script src="js/vendor/angular.js"&gt;&lt;/script&gt;</p><p class="daima">　&lt;script src="js/vendor/angular-ui-router.js"&gt;&lt;/script&gt;</p><p class="content">我们从node_modules/中加载的文件是Angular及其依赖，而从js/vendor/中加载的文件则是AngularJS及其依赖。</p><p class="content">但是你可能已经注意到了，我们还没有在HTML标签中加载任何<b>自己的</b>代码。要加载这些代码，就要使用System.js。</p><p class="content">●<b>配置System.js</b></p><p class="content">在这个例子中，我们准备把System.js用作模块加载器。</p><p class="reference"><img src="https://img30.360buyimg.com/ebookadmin/jfs/t20827/10/215994487/12231/293f6125/5b03d541Ndff69dec.jpg" class="inline1" alt="" href="./image/a-3.jpg" />　我们还可以使用webpack（就像在本书其他例子中所用的那样）或很多其他的加载器（比如requirejs等）。不过，System.js是一个很不错并且具有可伸缩性的加载器，常常和Angular一起使用。本章会提供一个漂亮的示例，向你展示如何通过System.js使用Angular。</p><p class="content">要配置System.js，需要在index.html的&lt;script&gt;标签中进行如下修改：</p><p class="daima">&lt;script src="resources/systemjs.config.js"&gt;&lt;/script&gt;</p><p class="daima">System.import（'ts/app.js'）</p><p class="daima">　　　.then（null, console.error.bind（console））;</p><p class="content">System.import（'ts/app.js'）说明该应用的入口点是ts/app.js文件。当我们写混合式Angular应用时，<b>Angular的代码会成为入口点</b>。这很容易理解，因为Angular提供了对AngularJS的向后兼容能力。我们很快就会看到如何引导该应用。</p><p class="content">这里要注意的另一个问题是，我们正在ts/目录下加载.js文件。为什么呢？这是因为TypeScript编译器会在页面加载时把这些文件编译成JavaScript。</p><p class="content">我们已经在resources/systemjs.config.js中配置好了System.js。此文件中包含了几乎标准化的配置方式，但现在我们要把AngularJS应用加载到Angular代码中，那就不得不添加一个特殊的属性interestAppNg1了，它指向我们的AngularJS应用。该选项让我们能在TypeScript代码中这样用：</p><p class="daima">import 'interestAppNg1'; // "bare import" for side-effects</p><p class="content">当模块加载器看到字符串'interestAppNg1'时，就会去./js/app.js中加载我们的AngularJS应用。</p><p class="content">packages属性指出ts包（package）中的文件将会具有.js扩展名，并使用System.js来注册（register）这种模块格式。</p><p class="reference"><img src="https://img30.360buyimg.com/ebookadmin/jfs/t18559/108/2587195628/17417/8ceb743d/5b03d540N559a889f.jpg" class="inline1" alt="" href="./image/a-9.jpg" />　TypeScript编译器可以输出多种模块格式。System.js的format需要与编译器输出的模块格式保持一致。这里register的模块格式之所以能直接使用，是因为我们在tsconfig.json中把compilerOptions.module指定成了"system"格式。</p><p class="center"><img src="https://img30.360buyimg.com/ebookadmin/jfs/t17728/48/2715397358/11728/16045511/5b03d541N9720f866.jpg" class="enlarge" alt="" href="./image/a-4.jpg" /></p><p class="content">要配置好System.js是很难的，有大量潜在选项。</p><p class="content">这不是一本关于模块加载器的书，事实上，只是深入讲解如何配置System.js和其他JavaScript模块加载器就足够写一整本书了。</p><p class="content">目前，我们不准备深入讨论模块加载器，不过如果你想了解更多，请参阅https：//github.com/systemjs/systemjs/blob/master/docs/config-api.md。</p><p class="reference"><img src="https://img30.360buyimg.com/ebookadmin/jfs/t20827/10/215994487/12231/293f6125/5b03d541Ndff69dec.jpg" class="inline1" alt="" href="./image/a-3.jpg" />　你想阅读关于JavaScript模块加载器的书吗？我们正在考虑写一本。如果你想及时收到通知，请在这里留下你的邮箱：http：//eepurl.com/bMOaEX。</p><p class="listTitle1 biaoti8">16.6.2 引导混合式应用</p><p class="content">现在项目结构已经就绪，我们来启动这个应用吧。</p><p class="content">还记得吗？在AngularJS中，有两种方式可以启动应用：</p><p class="content">（1）使用ng-app指令，比如在HTML中写ng-app='interestApp'；</p><p class="content">（2）在JavaScript中使用angular.bootstrap。</p><p class="content">在混合式应用中，我们要使用来自UpgradeAdapter的<b>新引导方法</b>。</p><p class="content">我们还要改为从代码中启动应用，因此<b>请确保从index.html中移除了ng-app指令</b>。</p><p class="content">一个最简的启动代码是这样的：</p><p class="daima">// code/conversion/hybrid/ts/app.ts</p><p class="daima">import {</p><p class="daima">　NgModule,</p><p class="daima">　forwardRef</p><p class="daima">} from '@angular/core';</p><p class="daima">import { CommonModule } from '@angular/common';</p><p class="daima">import { BrowserModule } from '@angular/platform-browser';</p><p class="daima">import { UpgradeAdapter } from '@angular/upgrade';</p><p class="daima">declare var angular：any;</p><p class="daima">import 'interestAppNg1'; // "bare import" for side-effects</p><p class="daima">/*</p><p class="daima">* Create our upgradeAdapter</p><p class="daima">*/</p><p class="daima">const upgradeAdapter：UpgradeAdapter = new UpgradeAdapter（</p><p class="daima">　forwardRef（（）=&gt; MyAppModule））; // &lt;—— notice forward reference</p><p class="daima">// ……</p><p class="daima">// upgrade and downgrade components in here</p><p class="daima">// ……</p><p class="daima">/*</p><p class="daima">* Create our app's entry NgModule</p><p class="daima">*/</p><p class="daima">@NgModule（{</p><p class="daima">　declarations：[ MyNg2Component, …… ],</p><p class="daima">　imports：[</p><p class="daima">　　CommonModule,</p><p class="daima">　　BrowserModule</p><p class="daima">　],</p><p class="daima">　providers：[ MyNg2Services, …… ]</p><p class="daima">}）</p><p class="daima">class MyAppModule { }</p><p class="daima">/*</p><p class="daima">* Bootstrap the App</p><p class="daima">*/</p><p class="daima">upgradeAdapter.bootstrap（document.body, ['interestApp']）;</p><p class="content">我们先导入了UpgradeAdapter，然后创建它的实例upgradeAdapter。</p><p class="content">不过，UpgradeAdapter的构造函数需要一个NgModule，它用于启动我们的Angular应用，但我们还没有定义它呢！要解决这个问题，就用forwardRef函数来取得NgModule的“前向引用”（后面会声明它）。</p><p class="content">当我们定义自己的NgModule也就是MyAppModule时（具体到这个应用中，它应该是InterestAppModule），写法和定义其他Angular的NgModule没有区别：我们放进了声明（declarations）、导入（imports）和提供者（providers）等。</p><p class="content">最后，我们告诉upgradeAdapter在document.body元素上bootstrap此应用，并指定了<b>AngularJS应用</b>的模块名。</p><p class="content">这将会在启动Angular应用的同时启动AngularJS应用！接下来，我们就开始一点一点地用Angular替换掉它。</p><p class="listTitle1 biaoti8">16.6.3 我们要升级什么</p><p class="content">先来讨论一下这个例子中的哪些部分需要迁移到Angular，哪些仍然留在AngularJS。</p><p class="content">●<b>首页</b></p><p class="content">需要注意的第一点是，我们仍将使用AngularJS来管理路由。当然，Angular有自己的路由，你可以在第7章中读到它。但是如果你正在构建一个混合式应用，很可能已经用AngularJS配置过很多路由了。因此，在这个例子中，我们仍然沿用ui-router作为路由体系。</p><p class="content">在首页中，我们准备把Angular的组件嵌套在AngularJS的指令中。在这个例子中，就是把“图钉控件”转变成Angular的组件（如图16-6所示）。也就是说，我们的pin指令将调用Angular的pin-controls组件，而pin-controls组件负责渲染出用来表示收藏的心型图标。</p><p class="center"><img src="https://img30.360buyimg.com/ebookadmin/jfs/t19846/19/646373664/139537/b683ec36/5b03d541N9ae72b08.jpg" class="enlarge" alt="" href="./image/a-154.jpg" /></p><p class="descrip"><b>图16-6　首页的AngularJS和Angular组件</b></p><p class="content">尽管这是一个很小的例子，但它展示了一种强有力的想法：如何在ng的不同版本之间无缝地交换数据。</p><p class="content">●<b>About页</b></p><p class="content">我们也会在About页上使用AngularJS来实现路由和页眉。不过，在About页上，我们将把整个表单替换成Angular的组件：AddPinComponent（如图16-7所示）。</p><p class="center"><img src="https://img30.360buyimg.com/ebookadmin/jfs/t17053/364/2629319487/85156/89a760d3/5b03d541N8c1551f1.jpg" class="enlarge" alt="" href="./image/a-155.jpg" /></p><p class="descrip"><b>图16-7　About页的AngularJS和Angular组件</b></p><p class="content">回想一下，该表单会往PinsService上添加一个新的图钉。在这个例子中，我们需要通过某种方式来让Angular的AddPinComponent访问到AngularJS的PinsService。</p><p class="content">另外，在添加新的图钉之后，该应用应该自动导航到首页。不过，要想改变当前路由，我们需要在Angular的AddPinComponent中使用来自AngularJS中ui-router库的$state服务。因此，我们同样需要确保$state服务也能在AddPinComponent中使用。</p><p class="content">●<b>服务</b></p><p class="content">我们刚才说过，有两个AngularJS的服务将会<b>升级</b>到Angular：</p><p class="content">●PinsService</p><p class="content">●$state</p><p class="content">不过我们也想看看如何把一个Angular服务降级，以供AngularJS使用。为此，我们稍后会用TypeScript/Angular来创建一个AnalyticsService服务，并把它共享给AngularJS。</p><p class="content">●<b>盘点</b></p><p class="content">概括起来，我们准备讲解下列内容：</p><p class="content">●把Angular的PinControlsComponent降级到AngularJS（用来实现收藏按钮）；</p><p class="content">●把Angular的AddPinComponent降级到AngularJS（用来实现新增图钉页面）；</p><p class="content">●把Angular的AnalyticsService降级到AngularJS（用来进行事件记录）；</p><p class="content">●把AngularJS的PinsService升级到Angular（用来新增图钉）；</p><p class="content">●把AngularJS的$state服务升级到Angular（用来控制路由）。</p><p class="listTitle1 biaoti8">16.6.4 插一小段内容：类型文件</p><p class="content">TypeScript最美妙的一点就是编译时类型检查。不过，如果你正在构建一个混合式应用，那么估计你打算集成到项目中的JavaScript代码大部分是无类型的。</p><p class="content">当你试图在TypeScript中使用JavaScript代码时，可能会收到编译器错误，因为编译器不知道你的JavaScript对象结构如何。你可以尝试把它们全部转换成&lt;any&gt;，但这样不但看起很来丑而且容易出错。</p><p class="content">更好的方案是给TypeScript编译器提供自定义<b>类型注解</b>。然后，编译器就能用这些类型信息来强化你的JavaScript代码了。</p><p class="content">比如，还记得我们是怎样在AngularJS版本的makeNewPin中创建图钉对象的吗？</p><p class="reference"><b>code/conversion/AngularJS/js/app.js</b></p><p class="daima">　var makeNewPin = function（）{</p><p class="daima">　　return {</p><p class="daima">　　　"title"："Steampunk Cat",</p><p class="daima">　　　"description"："A cat wearing goggles",</p><p class="daima">　　　"user_name"："me",</p><p class="daima">　　　"avatar_src"："images/avatars/me.jpg",</p><p class="daima">　　　"src"："/images/pins/cat.jpg",</p><p class="daima">　　　"url"："http：//cats.com",</p><p class="daima">　　　"faved"：false,</p><p class="daima">　　　"id"：Math.floor（Math.random（）* 10000）.toString（）</p><p class="daima">　　}</p><p class="daima">　}</p><p class="daima">　ctrl.newPin = makeNewPin（）;</p><p class="content">如果能把这些对象的结构告诉编译器该多好！那样就不用到处求助于any了。</p><p class="content">此外，我们准备在Angular/TypeScript中使用ui-router中的$state服务，同样要把这个服务中有哪些可用的函数告诉编译器。</p><p class="content">因此，虽然为TypeScript提供自定义类型信息是TypeScript的分内之事（与Angular无关），但我们还是得亲力亲为。现在之所以还缺少这么多类型定义文件，是因为TypeScript才发布没多久，仍然相对较新。</p><p class="content">在本节中，我会告诉你如何为TypeScript制作自定义类型文件（custom typing）。</p><p class="reference"><img src="https://img30.360buyimg.com/ebookadmin/jfs/t18559/108/2587195628/17417/8ceb743d/5b03d540N559a889f.jpg" class="inline1" alt="" href="./image/a-9.jpg" />　如果你已经很熟悉如何创建和使用TypeScript的类型定义文件，请放心大胆地跳过本节。</p><p class="content">●<b>类型文件</b></p><p class="content">在TypeScript中，可以通过书写<b>类型定义文件</b>（typing definition file）来描述我们的代码结构。类型定义文件通常以扩展名.d.ts结尾。</p><p class="content">当我们写TypeScript代码时，通常不用写.d.ts文件，因为TypeScript文件本身已经包含了类型信息。只有当要为某些外来的JavaScript代码添加类型信息时，才需要写.d.ts文件。</p><p class="content">例如，为了描述我们的图钉对象，可以为它写一个interface。</p><p class="reference"><b>code/conversion/hybrid/js/app.d.ts</b></p><p class="daima">　export interface Pin {</p><p class="daima">　　title：string;</p><p class="daima">　　description：string;</p><p class="daima">　　user_name：string;</p><p class="daima">　　avatar_src：string;</p><p class="daima">　　src：string;</p><p class="daima">　　url：string;</p><p class="daima">　　faved：boolean;</p><p class="daima">　　id：string;</p><p class="daima">　}</p><p class="content">注意，我们不是在声明一个类，也没有创建实例，而是定义了接口的形态（类型）。</p><p class="content">要使用.d.ts文件，需要告诉TypeScript它们在哪里。最简单的方式就是修改tsconfig.json文件。比如，假设有一个名为js/app.d.ts的文件，我们就可以像这样添加它：</p><p class="daima">　// tsconfig.json</p><p class="daima">　"compilerOptions"：{ …… },</p><p class="daima">　"files"：[</p><p class="daima">　　"ts/app.ts",</p><p class="daima">　　"js/app.d.ts"</p><p class="daima">　],</p><p class="daima">　// more……</p><p class="content">仔细看这里的文件路径。我们要从ts/app.ts中加载TypeScript，从js/目录下加载app.d.ts文件。这是因为js/app.d.ts文件是为js/app.js（这是AngularJS的JavaScript文件，而不是Angular的TypeScript文件）准备的类型文件。</p><p class="content">我们这就一点点把app.d.ts写出来。首先来看一个现有工具typings，以帮助我们使用第三方TypeScript定义文件。</p><p class="content">●<b>使用typings管理第三方库</b></p><p class="content">typings是一个用来为第三方库管理TypeScript类型定义文件的工具。</p><p class="content">我们准备使用angular-ui-router，所以要用typings来安装angular-ui-router的类型信息。下面是操作步骤。</p><p class="content">先安装好typings，可以用命令npm install -g typings来安装。</p><p class="content">接下来，配置一个typings.json文件，可以用命令typings init来创建（或者使用现成的）。</p><p class="content">然后，我们通过命令typings install angular-ui-router ——save来安装所需的包。</p><p class="content">注意，typings命令创建了一个typings目录，其中包含文件browser.d.ts。这个browser.d.ts文件是所有<b>被typings管理的类型定义文件</b>的总入口点。也就是说，如果你写了自己的类型定义文件，那么它们不会被包含在这里，但通过typings工具安装的类型定义都会被加载到此文件的reference标签下。</p><p class="reference"><img src="https://img30.360buyimg.com/ebookadmin/jfs/t17728/48/2715397358/11728/16045511/5b03d541N9720f866.jpg" class="inline1" alt="" href="./image/a-4.jpg" />　不要直接修改typings/browser.d.ts文件！typings会替你管理这个文件。如果你修改了它，那么这些修改就会被覆盖。</p><p class="content">现在，我们有了类型定义文件typings/browser.d.ts，该如何使用它呢？我们得先把它告诉编译器才行。可以通过tsconfig.json来做到这一点：</p><p class="daima">　// tsconfig.json</p><p class="daima">　"compilerOptions"：{ …… },</p><p class="daima">　"files"：[</p><p class="daima">　　"typings/browser.d.ts",</p><p class="daima">　　"ts/app.ts",</p><p class="daima">　　"js/app.d.ts"</p><p class="daima">　],</p><p class="daima">　// more……</p><p class="content">注意，我们把typings/browser.d.ts文件添加到了files数组中。这会告诉编译器我们要在编译时包含typings下的类型信息。</p><p class="center"><img src="https://img30.360buyimg.com/ebookadmin/jfs/t20827/10/215994487/12231/293f6125/5b03d541Ndff69dec.jpg" class="enlarge" alt="" href="./image/a-3.jpg" /></p><p class="content">假如我们要加载另一个库（比如underscore），而且同样希望用System.js加载它，该怎么办呢？</p><p class="content">整体思路是，你要：（1）让类型信息在编译时可用；（2）让代码在运行时可用。</p><p class="content">具体办法如下。</p><p class="content">（1）typings install underscore：安装类型信息文件。</p><p class="content">（2）npm install underscore：在node_modules中安装JavaScript文件。</p><p class="content">（3）在index.html中调用System.config的地方往paths下增加一句underscore：'./node_modules/underscore/underscore.js'。</p><p class="content">（4）然后在TypeScript中通过import * as _ from 'underscore';导入下划线。</p><p class="content">（5）最后使用下划线，就像这样：let foo = _.map（[1,2,3],（x）=&gt; x + 1）;。</p><p class="center"><img src="https://img30.360buyimg.com/ebookadmin/jfs/t17728/48/2715397358/11728/16045511/5b03d541N9720f866.jpg" class="enlarge" alt="" href="./image/a-4.jpg" /></p><p class="content">我们已经在这个应用中做完了typings install，所以你不必自己安装这些依赖了。</p><p class="content">实际上，如果你运行typings install，将会收到一个错误：</p><p class="daima">node_modules/angular2/typings/angular-protractor/angular-protractor.d.ts（1679,13\）：error TS2403：Subsequent variable declarations must have the same type.　Vari\able '$' must be of type 'JQueryStatic', but here has type 'cssSelectorHelper'.</p><p class="content">这个bug是因为jquery和angular的类型信息都试图把一个类型赋给$变量。在本书出版时，临时性的解决方案是打开typings/jquery/jquery.d.ts文件，并注释掉这一行：</p><p class="daima">// declare var $：JQueryStatic; // - ng-book told me to comment this</p><p class="content">当然，如果你想在TypeScript中通过$来访问jQuery特有的类型信息，就会出错（不过本例中不存在这种情况）。</p><p class="content">●<b>自定义类型文件</b></p><p class="content">能使用现成的第三方类型定义文件固然好，不过还有一些场景是找不到现有类型定义文件的，特别是我们自己写的代码。</p><p class="content">通常，当我们写自定义类型信息文件时，会把它和相应的JavaScript代码放在一起，因此我们来创建一个js/app.d.ts文件。</p><p class="reference"><b>code/conversion/hybrid/js/app.d.ts</b></p><p class="daima">declare module interestAppNg1 {</p><p class="daima">　export interface Pin {</p><p class="daima">　　title：string;</p><p class="daima">　　description：string;</p><p class="daima">　　user_name：string;</p><p class="daima">　　avatar_src：string;</p><p class="daima">　　src：string;</p><p class="daima">　　url：string;</p><p class="daima">　　faved：boolean;</p><p class="daima">　　id：string;</p><p class="daima">　}</p><p class="daima">　export interface PinsService {</p><p class="daima">　　pins（）：Promise&lt;Pin[]&gt;;</p><p class="daima">　　addPin（pin：Pin）：Promise&lt;any&gt;;</p><p class="daima">　}</p><p class="daima">}</p><p class="daima">declare module 'interestAppNg1' {</p><p class="daima">　export = interestAppNg1;</p><p class="daima">}</p><p class="content">我们用declare关键字来制作“周边声明”（ambient declaration），意思是我们定义了一个并非来自于TypeScript文件的变量。在这个例子中，我们声明了两个接口：</p><p class="content">（1）Pin</p><p class="content">（2）PinsService</p><p class="content">Pin接口用来描述图钉对象的属性名及其值类型。</p><p class="content">PinsService接口则用来描述这个PinsService中两个方法的类型。</p><p class="content">●pins（）返回一个由Pin数组构成的Promise；</p><p class="content">●addPin（）接收一个Pin参数，并返回一个Promise。</p><p class="center"><img src="https://img30.360buyimg.com/ebookadmin/jfs/t18559/108/2587195628/17417/8ceb743d/5b03d540N559a889f.jpg" class="enlarge" alt="" href="./image/a-9.jpg" /></p><p class="content"><b>学习写类型定义文件的更多知识</b></p><p class="content">如果要学习关于写.d.ts文件的更多知识，下列链接会很有帮助：</p><p class="content">●“TypeScript手册：与其他JavaScript库协同工作”（http：//www.types-criptlang.org/Handbook#modules-working-with-other-javascript-libraries）</p><p class="content">●“TypeScript手册：书写类型定义文件”（https：//github.com/Microsoft/TypeScript-Handbook/blob/master/pages/Writing％20Definition％20Files.md）</p><p class="content">●“快速提示：TypeScript的declare关键字”（http：//blogs.microsoft.co.il/gilf/2013/07/22/quick-tip-typescript-declare-keyword/）</p><p class="content">你可能已经注意到了，我们并没有在AngularJS的JavaScript代码的任何地方声明令牌interestAppNg1。这是因为interestAppNg1只是我们用来在TypeScript代码中引用这些JavaScript代码时所用的标识符，并不是类型的一部分。</p><p class="content">我们已经完成了这个文件，可以导入这些类型了，就像这样：</p><p class="daima">import { Pin, PinsService } from 'interestAppNg1';</p><p class="listTitle1 biaoti8">16.6.5 写Angular的PinControlsComponent</p><p class="content">我们刚刚明白了类型信息，那就言归正传，继续看混合式应用吧。</p><p class="content">我们首先要做的是写一个Angular版的PinControlsComponent，这样才能把Angular的组件嵌入到AngularJS的指令中。PinControlsComponent为收藏的图钉显示心型图标，点击它就可以来回切换状态。</p><p class="content">先从导入Pin类型开始，然后定义另一些需要的常量。</p><p class="reference"><b>code/conversion/hybrid/ts/components/PinControlsComponent.ts</b></p><p class="daima">/*</p><p class="daima">* PinControls：a component that holds the controls for a particular pin</p><p class="daima">*/</p><p class="daima">import {</p><p class="daima">　Component,</p><p class="daima">　Input,</p><p class="daima">　Output,</p><p class="daima">　EventEmitter</p><p class="daima">} from '@angular/core';</p><p class="daima">import { NgIf } from '@angular/common';</p><p class="daima">import { Pin } from 'interestAppNg1';</p><p class="content">接下来写@Component注解。</p><p class="reference"><b>code/conversion/hybrid/ts/components/PinControlsComponent.ts</b></p><p class="daima">@Component（{</p><p class="daima">　selector：'pin-controls',</p><p class="daima">　template：`</p><p class="daima">&lt;div class="controls"&gt;</p><p class="daima">　&lt;div class="heart"&gt;</p><p class="daima">　　&lt;a（click）="toggleFav（）"&gt;</p><p class="daima">　　　&lt;img src="/images/icons/Heart-Empty.png" *ngIf="！pin.faved" /&gt;</p><p class="daima">　　　&lt;img src="/images/icons/Heart-Red.png" *ngIf="pin.faved" /&gt;</p><p class="daima">　　&lt;/a&gt;</p><p class="daima">　&lt;/div&gt;</p><p class="daima">&lt;/div&gt;</p><p class="daima">　`</p><p class="daima">}）</p><p class="content">注意，这里匹配的是pin-controls元素。</p><p class="content">我们的模板和AngularJS版本的很像，只是把（click）和*ngIf改成了用Angular的模板语法。</p><p class="content">现在的组件定义类变成了下面这样。</p><p class="reference"><b>code/conversion/hybrid/ts/components/PinControlsComponent.ts</b></p><p class="daima">export class PinControlsComponent {</p><p class="daima">　@Input（）pin：Pin;</p><p class="daima">　@Output（）faved：EventEmitter&lt;Pin&gt; = new EventEmitter&lt;Pin&gt;（）;</p><p class="daima">　toggleFav（）：void {</p><p class="daima">　　this.faved.next（this.pin）;</p><p class="daima">　}</p><p class="daima">}</p><p class="content">注意，我们并没有在@Component注解中指定inputs和outputs，而是直接在类的属性上使用了@Input和@Output注解。用这种方式为属性提供类型信息更加简便。</p><p class="content">该组件将接收一个pin参数作为输入，也就是我们管理的Pin对象。</p><p class="content">该组件指定了一个名叫faved的输出参数。这跟我们在AngularJS应用中的用法略有不同。如果你查看toggleFav的实现，会发现我们所做的是通过EventEmitter把当前图钉发给了外界。</p><p class="content">这是因为我们已经在AngularJS中实现了更改faved状态的方法，所以不希望在Angular中重新实现一模一样的功能（但你也可能希望再次实现，这取决于你们开发组内的约定）。</p><p class="listTitle1 biaoti8">16.6.6 使用Angular的PinControlsComponent</p><p class="content">有了Angular的pin-controlls组件，我们就可以在模板中使用它了。现在的pin.html模板变成了下面这样。</p><p class="reference"><b>code/conversion/hybrid/templates/pin.html</b></p><p class="daima">&lt;div class="col-sm-6 col-md-4"&gt;</p><p class="daima">　&lt;div class="thumbnail"&gt;</p><p class="daima">　　&lt;div class="content"&gt;</p><p class="daima">　　　&lt;img ng-src="{{pin.src}}" class="img-responsive"&gt;</p><p class="daima">　　　&lt;div class="caption"&gt;</p><p class="daima">　　　　&lt;h3&gt;{{pin.title}}&lt;/h3&gt;</p><p class="daima">　　　　&lt;p&gt;{{pin.description | truncate：100}}&lt;/p&gt;</p><p class="daima">　　　&lt;/div&gt;</p><p class="daima">　　　&lt;div class="attribution"&gt;</p><p class="daima">　　　　&lt;img ng-src="{{pin.avatar_src}}" class="img-circle"&gt;</p><p class="daima">　　　　&lt;h4&gt;{{pin.user_name}}&lt;/h4&gt;</p><p class="daima">　　　&lt;/div&gt;</p><p class="daima">　　&lt;/div&gt;</p><p class="daima">　　&lt;div class="overlay"&gt;</p><p class="daima">　　　&lt;pin-controls [pin]="pin"</p><p class="daima">　　　　　　　　　　（faved）="toggleFav（$event）"&gt;&lt;/pin-controls&gt;</p><p class="daima">　　&lt;/div&gt;</p><p class="daima">　&lt;/div&gt;</p><p class="daima">&lt;/div&gt;</p><p class="content">该模板是属于AngularJS指令的，因此我们可以在里面使用AngularJS的指令，比如ng-src。不过，要注意使用Angular中pin-controls组件的那一行：</p><p class="daima">&lt;pin-controls [pin]="pin"</p><p class="daima">　　　　　　　（faved）="toggleFav（$event）"&gt;&lt;/pin-controls&gt;</p><p class="content">有意思的是，我们在同时使用Angular输入属性的方括号语法[pin]以及Angular输出属性的圆括号语法（faved）。</p><p class="content">在混合式应用中，<b>当你在AngularJS中使用Angular指令时，仍然可以照常使用Angular的语法</b>。</p><p class="content">通过输入属性[pin]，可以把来自AngularJS指令scope上的pin属性传进去。</p><p class="content">在输出参数（faved）中，我们调用了AngularJS指令scope上的toggleFav函数。注意看这里的实现方式：我们没有在Angular指令中修改pin.faved状态（虽然我们也能这么做）；反之，我们只是让Angular的PinControlsComponent在调用toggleFav的时候把这个pin发给外界。（如果没看明白，请再回头看看PinControlsComponent的toggleFav。）</p><p class="content">我们这么做是为了告诉你：可以保持AngularJS中的现有功能（scope.toggleFav）不变，只把组件迁移到Angular。在这个例子中，AngularJS的pin指令监听了Angular PinControlsComponent上的faved事件。</p><p class="content">如果你刷新这个页面，可能会注意到它无法正常工作，那是因为我们还缺少一个很重要的步骤：把PinControlsComponent降级到AngularJS。</p><p class="listTitle1 biaoti8">16.6.7 把Angular的PinControlsComponent降级到AngularJS</p><p class="content">要想让我们的组件跨越Angular和AngularJS的界线，最后一步是使用upgradeAdapter来降级这些组件（或者升级，我们稍后会看到）。</p><p class="content">我们在app.ts文件中执行这些降级工作（也就是调用upgradeAdapter.bootstrap的地方）。</p><p class="content">首先，我们需要导入必备的angular库。</p><p class="reference"><b>code/conversion/hybrid/ts/app.ts</b></p><p class="daima">import {</p><p class="daima">　NgModule,</p><p class="daima">　forwardRef</p><p class="daima">} from '@angular/core';</p><p class="daima">import { CommonModule } from '@angular/common';</p><p class="daima">import {</p><p class="daima">　FormsModule,</p><p class="daima">} from '@angular/forms';</p><p class="daima">import { BrowserModule } from "@angular/platform-browser";</p><p class="daima">import { UpgradeAdapter } from '@angular/upgrade';</p><p class="daima">declare var angular：any;</p><p class="daima">import 'interestAppNg1'; // "bare import" for side-effects</p><p class="content">然后，我们用（几乎）标准的AngularJS方式来创建一个.directive。</p><p class="reference"><b>code/conversion/hybrid/ts/app.ts</b></p><p class="daima">angular.module（'interestApp'）</p><p class="daima">　.directive（'pinControls',</p><p class="daima">　　　　　　 upgradeAdapter.downgradeNg2Component（PinControlsComponent））</p><p class="content">记住我们已经导入了'interestAppNg1'，它会加载我们的AngularJS应用，而AngularJS应用中调用了angular.module（'interestApp', []）。也就是说，我们的AngularJS应用已经通过angular注册好了interestApp模块。</p><p class="content">现在，我们要通过调用angular.module（'interestApp'）来找到该模块，然后把指令添加到其中，就像我们在AngularJS中的标准做法那样。</p><p class="center"><img src="https://img30.360buyimg.com/ebookadmin/jfs/t20827/10/215994487/12231/293f6125/5b03d541Ndff69dec.jpg" class="enlarge" alt="" href="./image/a-3.jpg" /></p><p class="content"><b>angular.module的获取（getter）和设置（setter）语法</b></p><p class="content">还记得吗？当往angular.module函数的第二个参数中传入一个数组时，我们就是在<b>创建</b>模块。比如angular.module（'foo', []）将创建一个名叫foo的模块。我们非正式地将其称为设置语法。</p><p class="content">同样，如果我们省略了这个数组，就是在<b>获取</b>一个模块（假设它已经存在）。比如angular.module（'foo'）将获取foo模块。我们称其为获取语法。</p><p class="reference"><img src="https://img30.360buyimg.com/ebookadmin/jfs/t17728/48/2715397358/11728/16045511/5b03d541N9720f866.jpg" class="inline1" alt="" href="./image/a-4.jpg" />　在这个例子中，如果我们忘了这项限制，并且在app.ts（Angular）中调用angular.module（'interestApp', []），就会意外地覆盖现有的interestApp模块。你的应用将无法正常工作。千万要小心！</p><p class="content">我们调用.directive并创建了一个名叫'pinControls'的指令。这是一种标准的AngularJS实践。它的第二个参数是指令定义对象（directive definition object，DDO），我们不会手动创建DDO，而是调用upgradeAdapter.downgradeNg2Component。</p><p class="content">downgradeNg2Component会把我们的PinControlsComponent转换成与AngularJS兼容的指令。干净！漂亮！</p><p class="content">刷新一下，你会发现收藏功能仍然正常工作（如图16-8所示），但我们已经把目前的实现方式改成在AngularJS中嵌入Angular了！</p><p class="center"><img src="https://img30.360buyimg.com/ebookadmin/jfs/t20326/208/205367437/57401/b9da06ed/5b03d540Nece2d82e.jpg" class="enlarge" alt="" href="./image/a-156.jpg" /></p><p class="descrip"><b>图16-8　收藏功能仍然很棒</b></p><p class="listTitle1 biaoti8">16.6.8 用Angular添加图钉</p><p class="content">接下来要用Angular组件对添加图钉的页面进行升级（如图16-9所示）。</p><p class="center"><img src="https://img30.360buyimg.com/ebookadmin/jfs/t19201/55/2619172609/63226/49e3936a/5b03d541N0b04f089.jpg" class="enlarge" alt="" href="./image/a-157.jpg" /></p><p class="descrip"><b>图16-9　新增图钉的表单</b></p><p class="content">回想一下，这个页面一共做了三件事：</p><p class="content">（1）为用户提供一个用来描述这个图钉的表单；</p><p class="content">（2）借助PinsService把新的图钉添加到图钉列表中；</p><p class="content">（3）把用户重定向到首页。</p><p class="content">我们来看看该如何在Angular中做到这些。</p><p class="content">Angular提供了一个强力的表单库，所以这没什么难度。那我们就来写一个正统的Angular表单吧。</p><p class="content">不过，PinsService仍然来自AngularJS。通常，我们会有很多来自AngularJS的既有服务，但又没那么多时间把它们都改写成Angular的。因此在这个例子中，我们仍然把PinsService保留为AngularJS对象，并<b>把它注入到Angular中</b>。</p><p class="content">与之类似，我们把来自AngularJS的ui-router作为路由系统。要想在ui-router中进行页面跳转，就得使用$state服务，它是一个AngularJS服务。</p><p class="content">那么，在这里要做的就是把PinsService和$state服务从AngularJS<b>升级</b>到Angular，这已经是最简易的方式了。</p><p class="listTitle1 biaoti8">16.6.9 把AngularJS的PinsService和$state升级到Angular</p><p class="content">要想升级AngularJS的服务，我们可以调用upgradeAdapter.upgradeNg1Provider。</p><p class="reference"><b>code/conversion/hybrid/ts/app.ts</b></p><p class="daima">/*</p><p class="daima">* Expose our AngularJS content to Angular</p><p class="daima">*/</p><p class="daima">upgradeAdapter.upgradeNg1Provider（'PinsService'）;</p><p class="daima">upgradeAdapter.upgradeNg1Provider（'$state'）;</p><p class="content">这样就足够了。现在我们可以把AngularJS的服务注入（@Inject）到Angular的组件中，就像这样：</p><p class="daima">class AddPinComponent {</p><p class="daima">　constructor（@Inject（'PinsService'）public pinsService：PinsService,</p><p class="daima">　　　　　　　@Inject（'$state'）public uiState：IStateService）{</p><p class="daima">　}</p><p class="daima">　// ……</p><p class="daima">　// now you can use this.pinsService</p><p class="daima">　// or this.uiState</p><p class="daima">　// ……</p><p class="daima">}</p><p class="content">在这个构造函数中，有几点需要注意。</p><p class="content">@Inject注解的意思是，我们要把参数中指定的可注入对象解析出来，赋值给紧随其后的变量。比如这里的pinsService将被赋值为我们在AngularJS中定义的服务PinsService。</p><p class="content">在TypeScript语法中，在constructor中使用public关键字其实是一种简写形式，用来把该变量赋值给this。也就是说，当我们写public pinsService时，其实是在做两件事：（1）在该类上定义一个实例属性pinsService；（2）把构造函数的参数pinsService赋值给this.pinsService。</p><p class="content">最终的效果是我们可以在这个类中访问this.pinsService了。</p><p class="content">最后，我们定义了所注入的两个服务的类型：PinsService和IStateService。</p><p class="content">PinsService来自我们以前定义过的app.d.ts。</p><p class="reference"><b>code/conversion/hybrid/js/app.d.ts</b></p><p class="daima">　export interface PinsService {</p><p class="daima">　　pins（）：Promise&lt;Pin[]&gt;;</p><p class="daima">　　addPin（pin：Pin）：Promise&lt;any&gt;;</p><p class="daima">　}</p><p class="content">IStateService来自ui-router的类型文件，它是我们以前用typings工具安装的。</p><p class="content">通过把这些服务的类型信息告诉TypeScript，我们在写代码时就可以享受类型检查带来的好处了。</p><p class="content">下面来写完AddPinComponent的剩余部分。</p><p class="listTitle1 biaoti8">16.6.10 写Angular版的AddPinComponent</p><p class="content">我们先从导入所需的类型信息开始。</p><p class="reference"><b>code/conversion/hybrid/ts/components/AddPinComponent.ts</b></p><p class="daima">/*</p><p class="daima">* AddPinComponent：a component that controls the "add pin" page</p><p class="daima">*/</p><p class="daima">import {</p><p class="daima">　Component,</p><p class="daima">　Inject</p><p class="daima">} from '@angular/core';</p><p class="daima">import { Pin, PinsService } from 'interestAppNg1';</p><p class="daima">import { IStateService } from 'angular-ui-router';</p><p class="content">注意，我们导入了自定义类型Pin和PinsService，还从angular-ui-router中导入了IStateService。</p><p class="content">●<b>AddPinComponent的@Component</b></p><p class="content">这个@Component注解非常简明。</p><p class="reference"><b>code/conversion/hybrid/ts/components/AddPinComponent.ts</b></p><p class="daima">@Component（{</p><p class="daima">　selector：'add-pin',</p><p class="daima">　templateUrl：'/templates/add-Angular.html'</p><p class="daima">}）</p><p class="content">●<b>AddPinComponent模板</b></p><p class="content">我们使用templateUrl来加载模板。在该模板中，我们的表单和AngularJS中的表单非常像，但所用的是Angular的表单指令集。</p><p class="reference"><img src="https://img30.360buyimg.com/ebookadmin/jfs/t20827/10/215994487/12231/293f6125/5b03d541Ndff69dec.jpg" class="inline1" alt="" href="./image/a-3.jpg" />　我们在这里不准备深入讲解ngModel/ngSubmit。如果你想深入了解Angular表单的工作原理，请阅读第5章，我们在那里对表单进行了详细讲解。</p><p class="reference"><b>code/conversion/hybrid/templates/add-Angular.html</b></p><p class="daima">&lt;div class="container"&gt;</p><p class="daima">　&lt;div class="row"&gt;</p><p class="daima">　　&lt;form（ngSubmit）="onSubmit（）"</p><p class="daima">　　　　　class="form-horizontal"&gt;</p><p class="daima">　　　&lt;div class="form-group"&gt;</p><p class="daima">　　　　&lt;label for="title"</p><p class="daima">　　　　　　　 class="col-sm-2 control-label"&gt;Title&lt;/label&gt;</p><p class="daima">　　　　&lt;div class="col-sm-10"&gt;</p><p class="daima">　　　　　&lt;input type="text"</p><p class="daima">　　　　　　　　 class="form-control"</p><p class="daima">　　　　　　　　 id="title"</p><p class="daima">　　　　　　　　 name="title"</p><p class="daima">　　　　　　　　 placeholder="Title"</p><p class="daima">　　　　　　　　 [（ngModel）]="newPin.title"&gt;</p><p class="daima">　　　　&lt;/div&gt;</p><p class="content">这里使用了两个指令：ngSubmit和ngModel。</p><p class="content">我们在表单上使用了（ngSubmit）。这样当表单被提交时，就会调用onSubmit函数。（我们会在稍后的AddPinComponent控制器中定义onSubmit函数。）</p><p class="content">我们使用[（ngModel）]来把title输入框的值绑定到控制器中newPin.title的值。</p><p class="content">下面是完整的模板代码。</p><p class="reference"><b>code/conversion/hybrid/templates/add-Angular.html</b></p><p class="daima">&lt;div class="container"&gt;</p><p class="daima">　&lt;div class="row"&gt;</p><p class="daima">　　&lt;form（ngSubmit）="onSubmit（）"</p><p class="daima">　　　　　class="form-horizontal"&gt;</p><p class="daima">　　　&lt;div class="form-group"&gt;</p><p class="daima">　　　　&lt;label for="title"</p><p class="daima">　　　　　　　 class="col-sm-2 control-label"&gt;Title&lt;/label&gt;</p><p class="daima">　　　　&lt;div class="col-sm-10"&gt;</p><p class="daima">　　　　　&lt;input type="text"</p><p class="daima">　　　　　　　　 class="form-control"</p><p class="daima">　　　　　　　　 id="title"</p><p class="daima">　　　　　　　　 name="title"</p><p class="daima">　　　　　　　　 placeholder="Title"</p><p class="daima">　　　　　　　　 [（ngModel）]="newPin.title"&gt;</p><p class="daima">　　　　&lt;/div&gt;</p><p class="daima">　　　&lt;/div&gt;</p><p class="daima">　　　&lt;div class="form-group"&gt;</p><p class="daima">　　　　&lt;label for="description"</p><p class="daima">　　　　　　　 class="col-sm-2 control-label"&gt;Description&lt;/label&gt;</p><p class="daima">　　　　&lt;div class="col-sm-10"&gt;</p><p class="daima">　　　　　&lt;input type="text"</p><p class="daima">　　　　　　　　 class="form-control"</p><p class="daima">　　　　　　　　 id="description"</p><p class="daima">　　　　　　　　 name="description"</p><p class="daima">　　　　　　　　 placeholder="Description"</p><p class="daima">　　　　　　　　 [（ngModel）]="newPin.description"&gt;</p><p class="daima">　　　　&lt;/div&gt;</p><p class="daima">　　　&lt;/div&gt;</p><p class="daima">　　　&lt;div class="form-group"&gt;</p><p class="daima">　　　　&lt;label for="url"</p><p class="daima">　　　　　　　 class="col-sm-2 control-label"&gt;Link URL&lt;/label&gt;</p><p class="daima">　　　　&lt;div class="col-sm-10"&gt;</p><p class="daima">　　　　　&lt;input type="text"</p><p class="daima">　　　　　　　　 class="form-control"</p><p class="daima">　　　　　　　　 id="url"</p><p class="daima">　　　　　　　　 name="url"</p><p class="daima">　　　　　　　　 placeholder="Link URL"</p><p class="daima">　　　　　　　　 [（ngModel）]="newPin.url"&gt;</p><p class="daima">　　　　&lt;/div&gt;</p><p class="daima">　　　&lt;/div&gt;</p><p class="daima">　　　&lt;div class="form-group"&gt;</p><p class="daima">　　　　&lt;label for="url"</p><p class="daima">　　　　　　　　class="col-sm-2 control-label"&gt;Image URL&lt;/label&gt;</p><p class="daima">　　　　&lt;div class="col-sm-10"&gt;</p><p class="daima">　　　　　&lt;input type="text"</p><p class="daima">　　　　　　　　 class="form-control"</p><p class="daima">　　　　　　　　 id="url"</p><p class="daima">　　　　　　　　 name="url"</p><p class="daima">　　　　　　　　 placeholder="Image URL"</p><p class="daima">　　　　　　　　 [（ngModel）]="newPin.src"&gt;</p><p class="daima">　　　　&lt;/div&gt;</p><p class="daima">　　　&lt;/div&gt;</p><p class="daima">　　　&lt;div class="form-group"&gt;</p><p class="daima">　　　　&lt;div class="col-sm-offset-2 col-sm-10"&gt;</p><p class="daima">　　　　　&lt;button type="submit"</p><p class="daima">　　　　　　　　　class="btn btn-default"</p><p class="daima">　　　　　　　　　&gt;Submit&lt;/button&gt;</p><p class="daima">　　　　&lt;/div&gt;</p><p class="daima">　　　&lt;/div&gt;</p><p class="daima">　　　&lt;div *ngIf="saving"&gt;</p><p class="daima">　　　　Saving……</p><p class="daima">　　　&lt;/div&gt;</p><p class="daima">　　&lt;/form&gt;</p><p class="content">●<b>AddPinComponent控制器</b></p><p class="content">现在我们就可以定义AddPinComponent了。先从两个实例变量开始。</p><p class="reference"><b>code/conversion/hybrid/ts/components/AddPinComponent.ts</b></p><p class="daima">export class AddPinComponent {</p><p class="daima">　saving：boolean = false;</p><p class="daima">　newPin：Pin;</p><p class="content">saving会告诉用户我们正在进行保存，而newPin用于存储我们正在使用的Pin对象。</p><p class="reference"><b>code/conversion/hybrid/ts/components/AddPinComponent.ts</b></p><p class="daima">　constructor（@Inject（'PinsService'）private pinsService：PinsService,</p><p class="daima">　　　　　　　@Inject（'$state'）private uiState：IStateService）{</p><p class="daima">　　this.newPin = this.makeNewPin（）;</p><p class="daima">　}</p><p class="content">如前所述，我们用Inject在constructor中注入了这些服务，并且把this.newPin的值设置成了makeNewPin，也就是下面这个函数。</p><p class="reference"><b>code/conversion/hybrid/ts/components/AddPinComponent.ts</b></p><p class="daima">　makeNewPin（）：Pin {</p><p class="daima">　　return {</p><p class="daima">　　　title：'Steampunk Cat',</p><p class="daima">　　　description：'A cat wearing goggles',</p><p class="daima">　　　user_name：'me',</p><p class="daima">　　　avatar_src：'images/avatars/me.jpg',</p><p class="daima">　　　src：'/images/pins/cat.jpg',</p><p class="daima">　　　url：'http：//cats.com',</p><p class="daima">　　　faved：false,</p><p class="daima">　　　id：Math.floor（Math.random（）* 10000）.toString（）</p><p class="daima">　　};</p><p class="daima">　}</p><p class="content">这看起来很像AngularJS中的定义方式，不过这种方式现在的优点在于它是带类型信息的。</p><p class="content">当用户提交表单时，我们调用onSubmit，其定义如下所示。</p><p class="reference"><b>code/conversion/hybrid/ts/components/AddPinComponent.ts</b></p><p class="daima">　onSubmit（）：void {</p><p class="daima">　　this.saving = true;</p><p class="daima">　　console.log（'submitted', this.newPin）;</p><p class="daima">　　setTimeout（（）=&gt; {</p><p class="daima">　　　this.pinsService.addPin（this.newPin）.then（（）=&gt; {</p><p class="daima">　　　　this.newPin = this.makeNewPin（）;</p><p class="daima">　　　　this.saving = false;</p><p class="daima">　　　　this.uiState.go（'home'）;</p><p class="daima">　　　}）;</p><p class="daima">　　}, 2000）;</p><p class="daima">　}</p><p class="content">我们再次使用超时（timeout）技术来<b>模拟</b>通过向服务器发起请求来保存图钉的效果。这里我们使用的是setTimeout。下面对比一下在AngularJS中实现同样功能的写法。</p><p class="reference"><b>code/conversion/AngularJS/js/app.js</b></p><p class="daima">　ctrl.submitPin = function（）{</p><p class="daima">　　ctrl.saving = true;</p><p class="daima">　　$timeout（function（）{</p><p class="daima">　　　PinsService.addPin（ctrl.newPin）.then（function（）{</p><p class="daima">　　　　ctrl.newPin = makeNewPin（）;</p><p class="daima">　　　　ctrl.saving = false;</p><p class="daima">　　　　$state.go（'home'）;</p><p class="daima">　　　}）;</p><p class="daima">　　}, 2000）;</p><p class="daima">　}</p><p class="content">注意，我们在AngularJS中必须使用$timeout服务。为什么呢？这是因为AngularJS是基于摘要循环（digest loop）的。如果你在AngularJS中直接使用setTimeout，那么当调用回调函数时，它会处于Angular的控制范围之外。因此改动造成的影响不会扩散出来，除非某些代码触发了摘要循环（比如使用$scope.apply）。</p><p class="content">然而在Angular中，你可以直接使用setTimeout，因为Angular中的变更检测使用的是Zones，所以更加自动化。你再也不用担心摘要循环了，这太好了！</p><p class="content">在onSubmit中，我们通过下列代码调用了PinsService：</p><p class="daima">this.pinsService.addPin（this.newPin）.then（（）=&gt; {</p><p class="daima">// ……</p><p class="content">PinsService可以通过this.pinsService来访问，因为我们定义constructor时使用了特殊写法。编译器没有报错，这是因为我们已经在app.d.ts中声明过addPin接收一个Pin对象作为第一个参数。</p><p class="reference"><b>code/conversion/hybrid/js/app.d.ts</b></p><p class="daima">　export interface PinsService {</p><p class="daima">　　pins（）：Promise&lt;Pin[]&gt;;</p><p class="daima">　　addPin（pin：Pin）：Promise&lt;any&gt;;</p><p class="daima">　}</p><p class="content">我们还把this.newPin定义成了一个Pin对象。</p><p class="content">addPin解析完成后，我们把this.newPin重置为this.makeNewPin（）的结果，并设置this.saving = false。</p><p class="content">要返回首页，就要使用ui-router的$state服务。我们已经通过依赖注入把它存储到了this.uiState属性中，所以可以直接调用this.uiState.go（'home'）来变更状态。</p><p class="listTitle1 biaoti8">16.6.11 使用AddPinComponent</p><p class="content">我们现在就来使用AddPinComponent。</p><p class="content">●<b>降级Angular的AddPinComponent</b></p><p class="content">要想使用AddPinComponent，就得先把它降级。</p><p class="reference"><b>code/conversion/hybrid/ts/app.ts</b></p><p class="daima">angular.module（'interestApp'）</p><p class="daima">　.directive（'pinControls',</p><p class="daima">　　　　　　 upgradeAdapter.downgradeNg2Component（PinControlsComponent））</p><p class="daima">　.directive（'addPin',</p><p class="daima">　　　　　　 upgradeAdapter.downgradeNg2Component（AddPinComponent））;</p><p class="content">这会在AngularJS中创建一个addPin指令，它会匹配&lt;add-pin&gt;标签。</p><p class="content">●<b>路由到add-pin</b></p><p class="content">为了使用这个新的AddPinComponent页，就要把它放进AngularJS应用中的某个地方。这很简单，只要让路由器拿到这个add状态，并把&lt;add-pin&gt;指令放到模板中就可以了。</p><p class="reference"><b>code/conversion/hybrid/js/app.js</b></p><p class="daima">　　.state（'add', {</p><p class="daima">　　　template："&lt;add-pin&gt;&lt;/add-pin&gt;",</p><p class="daima">　　　url：'/add',</p><p class="daima">　　　resolve：{</p><p class="daima">　　　　'pins'：function（PinsService）{</p><p class="daima">　　　　　return PinsService.pins（）;</p><p class="daima">　　　　}</p><p class="daima">　　　}</p><p class="daima">　　}）</p><p class="listTitle1 biaoti8">16.6.12 把Angular的服务暴露给AngularJS</p><p class="content">目前，我们已经降级了Angular的组件使其能用在AngularJS中，还升级了AngularJS的服务使其能用在Angular中。但是当我们的应用开始升级到Angular时，可能会需要用TypeScript/Angular写一些服务，并把它暴露给AngularJS的代码。</p><p class="content">那么我们就在Angular中创建一个简单的“分析”（analytics）服务，用来记录事件。</p><p class="content">我们的想法是：在应用中有一个AnalyticsService，我们将调用它的recordEvent方法。在具体实现上，我们只会调用console.log来记录该事件，并把它存到一个数组中。这样做是为了把精力集中在最重要的事情上：描述如何把Angular的服务共享给AngularJS。</p><p class="listTitle1 biaoti8">16.6.13 实现AnalyticsService</p><p class="content">我们先来看看AnalyticsService的实现。</p><p class="reference"><b>code/conversion/hybrid/ts/services/AnalyticsService.ts</b></p><p class="daima">import { Injectable } from '@angular/core';</p><p class="daima">/**</p><p class="daima">* Analytics Service records metrics about what the user is doing</p><p class="daima">*/</p><p class="daima">@Injectable（）</p><p class="daima">export class AnalyticsService {</p><p class="daima">　events：string[] = [];</p><p class="daima">　public recordEvent（event：string）：void {</p><p class="daima">　　console.log（`Event：${event}`）;</p><p class="daima">　　this.events.push（event）;</p><p class="daima">　}</p><p class="daima">}</p><p class="daima">export var analyticsServiceInjectables：Array&lt;any&gt; = [</p><p class="daima">　{ provide：AnalyticsService, useClass：AnalyticsService }</p><p class="daima">];</p><p class="content">这里需要注意两点：recordEvent和Injectable。</p><p class="content">recordEvent很简明：我们接收一个event：string参数，输出它的日志，并且把它保存到events中。在现实世界的应用中，你可能会把它发给某个外部服务，比如Google分析或Mixpanel。</p><p class="content">要让该服务可注入，我们得做两件事：（1）为该类添加@Injectable注解；（2）把AnalyticsService这个令牌bind到该类。</p><p class="content">现在，Angular将会管理该服务的单例对象，而我们可以把它注入到任何需要它的地方了。</p><p class="listTitle1 biaoti8">16.6.14 把Angular的AnalyticsService降级到AngularJS</p><p class="content">在AngularJS中使用AnalyticsService服务之前，我们需要把它降级。</p><p class="content">把Angular服务降级到AngularJS的过程和指令的降级过程很相似，只不过多出了一个额外的步骤：得先确保AnayticsService出现在了我们这个NgModule的providers列表中。</p><p class="reference"><b>code/conversion/hybrid/ts/app.ts</b></p><p class="daima">@NgModule（{</p><p class="daima">　declarations：[</p><p class="daima">　　PinControlsComponent,</p><p class="daima">　　AddPinComponent</p><p class="daima">　],</p><p class="daima">　imports：[</p><p class="daima">　　CommonModule,</p><p class="daima">　　BrowserModule,</p><p class="daima">　　FormsModule</p><p class="daima">　],</p><p class="daima">　providers：[</p><p class="daima">　　AnalyticsService,</p><p class="daima">　]</p><p class="daima">}）</p><p class="daima">class InterestAppModule { }</p><p class="content">然后就可以使用downgradeNg2Provider了。</p><p class="reference"><b>code/conversion/hybrid/ts/app.ts</b></p><p class="daima">angular.module（'interestApp'）</p><p class="daima">　.factory（'AnalyticsService',</p><p class="daima">　　　　　 upgradeAdapter.downgradeNg2Provider（AnalyticsService））;</p><p class="content">我们先调用angular.module（'interestApp'）来取得AngularJS的模块，然后像在AngularJS中一样调用.factory。要想降级该服务，要调用upgradeAdapter.downgradeNg2Provider（AnalyticsService）。它会把我们的AnalyticsService包装到一个函数中，而该函数会把它适配成一个AngularJS的工厂（factory）。</p><p class="listTitle1 biaoti8">16.6.15 在AngularJS中使用AnalyticsService</p><p class="content">现在就可以把Angular的AnalyticsService注入到AngularJS中去了。假如我们想记录HomeController是什么时候被访问的，就可以像下面这样来记录此事件。</p><p class="reference"><b>code/conversion/hybrid/js/app.js</b></p><p class="daima">.controller（'HomeController', function（pins, AnalyticsService）{</p><p class="daima">　AnalyticsService.recordEvent（'HomeControllerVisited'）;</p><p class="daima">　this.pins = pins;</p><p class="daima">}）</p><p class="content">这里注入了AnalyticsService，就像它是AngularJS中的普通服务一样，然后调用recordEvent。真棒！</p><p class="content">我们可以在AngularJS中任何能使用依赖注入的地方使用该服务。比如，我们也可以把AnalyticsService注入到AngularJS的pin指令中。</p><p class="reference"><b>code/conversion/hybrid/js/app.js</b></p><p class="daima">.directive（'pin', function（AnalyticsService）{</p><p class="daima">　return {</p><p class="daima">　　restrict：'E',</p><p class="daima">　　templateUrl：'/templates/pin.html',</p><p class="daima">　　scope：{</p><p class="daima">　　　'pin'："=item"</p><p class="daima">　　},</p><p class="daima">　　link：function（scope, elem, attrs）{</p><p class="daima">　　　scope.toggleFav = function（）{</p><p class="daima">　　　　AnalyticsService.recordEvent（'PinFaved'）;</p><p class="daima">　　　　scope.pin.faved =！scope.pin.faved;</p><p class="daima">　　　}</p><p class="daima">　　}</p><p class="daima">　}</p><p class="daima">}）</p><p class="sectionTitle biaoti3"><a id="sec007"></a>16.7 总结</p><p class="content">现在我们掌握了把AngularJS应用升级到AngularJS/Angular混合式应用时所需的工具。AngularJS和Angular之间也有非常好的互操作性，这是因为Angular开发组付出了很多努力来对其进行简化。</p><p class="content">AngularJS和Angular的指令与服务之间能够互通，让应用升级变得非常容易。当然，我们不可能一夜之间就把AngularJS的应用升级到Angular，不过UpgradeAdapter能让我们不必把那些老代码扔掉就开始使用Angular。</p><p class="sectionTitle biaoti3"><a id="sec008"></a>16.8 参考资源</p><p class="content">如果你想了解关于混合式Angular应用的更多知识，可以参阅下列资源。</p><p class="content">●官方的Angular升级指南（中文版）：https：//angular.cn/docs/ts/latest/guide/upgrade.html</p><p class="content">●Angular升级模块的单元测试：https：//github.com/angular/angular/blob/master/modules/angular2/test/upgrade/upgrade_spec.ts</p><p class="content">●Angular中DowngradeNg2ComponentAdapter的源代码：https：//github.com/angular/angular/blob/master/modules/angular2/src/upgrade/downgrade_Angular_adapter.ts</p><ol class="mz-footnote"><li id="footnote1">http：//teropa.info/blog/2014/10/24/how-ive-improved-my-angular-apps-by-banning-ng-controller.html</li><li id="footnote2">视频地址：https：//www.youtube.com/watch?v=gNmWybAyBHI&gt;</li><li id="footnote3">https：//github.com/angular-ui/ui-router</li><li id="footnote4">http：//ng-book.com</li><li id="footnote5">http：//bower.io/</li></ol></body></html>